/*********************************
说明：
    贪吃蛇小游戏

编译：
    gcc snake.c -o ./snake

*********************************/

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <windows.h>
#include <string.h>

// 定义标记上下左右的明示常量
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4
#define ESC 5
#define FOOD 10
// 定义表示位置的结构体类型
typedef struct snake
{
    int x;
    int y;
    struct snake *next;
} snake;
// 定义全局变量
int score = 0;   // 当前得分
int speed = 200; // 存储当前速度
int status;
snake *tail, *head; // 存储蛇头蛇尾
snake *food, *q;    // q用于遍历链表

HANDLE hOUT;
void gotoxy(int x, int y);     // 设置光标位置
int choice(void);              // 载入游戏界面
int color(int c);              // 设置字体颜色
void printGame(void);          // 打印游戏界面
void printSnake(void);         // 打印蛇身
void printFood(void);          // 打印食物
void printTips(void);          // 打印提示
void snakeMove(void);          // 主操作函数
int biteSelf(void);            // 判断是否咬到了自己
int encounterWall(void);       // 判断是否撞墙
void keyboardControl(void);    // 获取击键
void speedUp(void);            // 加速
void speedDown(void);          // 减速
int endGame(void);             // 结束函数;
char *s_gets(char *st, int n); // 读取字符
void frees(snake *);           // 释放内存

int main(int argc, char *argv[])
{
    while (1)
    {
        if (choice() == 1)
            keyboardControl();
        else
        {
            gotoxy(5, 15);
            printf("按任意键返回");
            getchar(); // 去除前一个前导换行
            while (1)
            {
                if (getchar())
                {
                    system("cls");
                    break;
                }
            }
        }
    }
    frees(head);

    return 0;
}

void gotoxy(int x, int y)
{
    COORD c;
    c.X = x;
    c.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);
}

int choice(void)
{
    int yourchoice;
    // 画出界面
    gotoxy(35, 5);
    color(11);
    printf("\t贪吃蛇大作战\n");
    printf("\n\n");
    color(13);
    printf("\t\t★★★★★★★★  Snake!");
    printf("\t\t★★★★★★★★  Snake!");
    gotoxy(25, 15);
    color(12);
    printf("1.进入游戏\t2.查看说明\t3.退出游戏\n");
    color(11);
    printf("请选择:");
    scanf("%d", &yourchoice);
    switch (yourchoice)
    {
    case 1:
        system("cls");
        // 初始化
        printGame();
        printSnake();
        printFood();
        break;
    case 2:
        system("cls");
        printTips();
        break;
    case 3:
        system("cls");
        gotoxy(30, 10);
        color(11);
        printf("Bye!");
        exit(0);
    default:
        system("cls");
        printf("没有此序号,请输入1,2或3\n");
        Sleep(2000);
        system("cls");
    }
    return yourchoice;
}
int color(int c)
{
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); // 更改文字颜色
    return 0;
}
void printGame()
{
    int i, j;
    gotoxy(5, 5);
    printf("游戏载入中...请稍后");
    Sleep(2000);
    system("cls");

    // 打印上下界面
    for (i = 0; i <= 50; i += 2)
    {
        gotoxy(i, 0);
        printf("□");
        gotoxy(i, 25);
        printf("□");
    }
    // 打印左右界面
    for (i = 0; i <= 25; i += 1)
    {
        gotoxy(0, i);
        printf("□");
        gotoxy(50, i);
        printf("□");
    }
    // 打印中间网格
    for (i = 1; i <= 24; i += 1)
    {
        for (j = 2; j <= 48; j += 2)
        {
            gotoxy(j, i);
            color(11);
            printf("■");
        }
    }
    // 打印右侧的规则和计分栏
    gotoxy(60, 13);
    printf("当前分数:%d分,当前速度%d", score, speed);
    gotoxy(60, 15);
    printf("用↑ ↓ ← →分别控制蛇的移动\n");
    gotoxy(60, 18);
    printf("每次获取食物加10分  按下F1加速,F2减速,空格暂停\n");
    gotoxy(60, 20);
    printf("不能撞墙和咬到自己!");
    gotoxy(60, 22);
    printf("速度不低于100,不高于300");
}
void printSnake(void)
{
    int i;
    // 设定蛇尾(16,13),头插入,初始向右
    tail = (snake *)malloc(sizeof(snake));
    tail->x = 16;
    tail->y = 13;
    tail->next = NULL;
    // 设定初始蛇长是4
    for (i = 1; i <= 4; i++)
    {
        head = (snake *)malloc(sizeof(snake));
        head->next = tail;
        head->x = 16 + 2 * i;
        head->y = 13;
        tail = head; // 头成为尾
    }
    // 输出蛇身
    while (tail->next)
    {
        gotoxy(tail->x, tail->y);
        color(14);
        printf("★");
        tail = tail->next;
    }
}
void printFood(void)
{
    srand((unsigned)time(NULL)); // 利用时钟修改种子
    food = (snake *)malloc(sizeof(snake));
    food->x = 1; // 初始化x坐标
    while (food->x % 2 && food->x)
    {
        food->x = rand() % 46 + 2; // 2-48
    }
    food->y = rand() % 23 + 1; // 1-24
    q = head;                  // 不改变头遍历链表
    while (q->next)
    {
        if (q->x == food->x && q->y == food->y)
        {
            free(food);
            printFood();
        }
        else
        {
            gotoxy(food->x, food->y);
            color(12);
            printf("●");
            break;
        }
    }
}
void printTips(void)
{
    color(11);
    printf("***********Tips************\n");
    printf("1.采用合理的速度可以获得更高的分数哦!\n");
    printf("2.一定不要撞到自己或者两边的墙!\n");
    printf("3.游戏过程中按ESC退出游戏!\n");
}
void snakeMove(void)
{
    snake *snakenext;
    snakenext = (snake *)malloc(sizeof(snake));
    if (biteSelf())
    {
        gotoxy(60, 11);
        printf("咬到自己啦!");
        free(snakenext);
        Sleep(1500);
        system("cls");
        exit(0);
    }
    else if (encounterWall())
    {
        gotoxy(60, 11);
        printf("撞到墙啦!");
        free(snakenext);
        Sleep(1500);
        system("cls");
        exit(0);
    }
    else
    {
        // 前两个条件判断完成才开始移动
        Sleep(350 - speed);
        if (status == UP)
        {
            snakenext->x = head->x;
            snakenext->y = head->y - 1;
            snakenext->next = head;
            head = snakenext;
            q = head;
            if (snakenext->x == food->x && snakenext->y == food->y)
            {
                while (q)
                {
                    gotoxy(q->x, q->y);
                    color(14);
                    printf("★");
                    q = q->next;
                }
                score += FOOD;
                gotoxy(60, 13);
                printf("当前分数:%d分,当前速度%d", score, speed);
                printFood();
            }
            else
            {
                while (q->next->next)
                {
                    gotoxy(q->x, q->y);
                    color(14);
                    printf("★");
                    q = q->next;
                }
                gotoxy(q->next->x, q->next->y);
                color(11);
                printf("■");
                free(q->next);
                q->next = NULL;
            }
        }
        else if (status == DOWN)
        {
            snakenext->x = head->x;
            snakenext->y = head->y + 1;
            snakenext->next = head;
            head = snakenext;
            q = head;
            if (snakenext->x == food->x && snakenext->y == food->y)
            {
                while (q)
                {
                    gotoxy(q->x, q->y);
                    color(14);
                    printf("★");
                    q = q->next;
                }
                score += FOOD;
                gotoxy(60, 13);
                printf("当前分数:%d分,当前速度%d", score, speed);
                printFood();
            }
            else
            {
                while (q->next->next)
                {
                    gotoxy(q->x, q->y);
                    color(14);
                    printf("★");
                    q = q->next;
                }
                gotoxy(q->next->x, q->next->y);
                color(11);
                printf("■");
                free(q->next);
                q->next = NULL;
            }
        }
        else if (status == LEFT)
        {
            snakenext->x = head->x - 2;
            snakenext->y = head->y;
            snakenext->next = head;
            head = snakenext;
            q = head;
            if (snakenext->x == food->x && snakenext->y == food->y)
            {
                while (q)
                {
                    gotoxy(q->x, q->y);
                    color(14);
                    printf("★");
                    q = q->next;
                }
                score += FOOD;
                gotoxy(60, 13);
                printf("当前分数:%d分,当前速度%d", score, speed);
                printFood();
            }
            else
            {
                while (q->next->next)
                {
                    gotoxy(q->x, q->y);
                    color(14);
                    printf("★");
                    q = q->next;
                }
                gotoxy(q->next->x, q->next->y);
                color(11);
                printf("■");
                free(q->next);
                q->next = NULL;
            }
        }
        else if (status == RIGHT)
        {
            snakenext->x = head->x + 2;
            snakenext->y = head->y;
            snakenext->next = head;
            head = snakenext;
            q = head;
            if (snakenext->x == food->x && snakenext->y == food->y)
            {
                while (q)
                {
                    gotoxy(q->x, q->y);
                    color(14);
                    printf("★");
                    q = q->next;
                }
                score += FOOD;
                gotoxy(60, 13);
                printf("当前分数:%d分,当前速度%d", score, speed);
                printFood();
            }
            else
            {
                while (q->next->next)
                {
                    gotoxy(q->x, q->y);
                    color(14);
                    printf("★");
                    q = q->next;
                }
                gotoxy(q->next->x, q->next->y);
                color(11);
                printf("■");
                free(q->next);
                q->next = NULL;
            }
        }
    }
}
int biteSelf(void)
{
    int x = 0; // 默认未咬到自己
    q = head->next;
    // 遍历蛇身
    while (q->next)
    {
        if (q->x == head->x && q->y == head->y)
        {
            x = 1;
        }
        q = q->next;
    }

    return x;
}
int encounterWall(void)
{
    int x = 0; // 默认未撞到墙

    if (head->x == 0 || head->x == 50 || head->y == 0 || head->y == 25)
        x = 1;

    return x;
}
void keyboardControl(void)
{
    status = RIGHT; // 初始蛇向右移动
    while (1)
    {
        if (GetAsyncKeyState(VK_UP) && status != DOWN) // GetAsyncKeyState函数用来判断函数调用时指定虚拟键的状态
        {
            status = UP; // 如果蛇不是向下前进的时候，按上键，执行向上前进操作
        }
        else if (GetAsyncKeyState(VK_DOWN) && status != UP) // 如果蛇不是向上前进的时候，按下键，执行向下前进操作
        {
            status = DOWN;
        }
        else if (GetAsyncKeyState(VK_LEFT) && status != RIGHT) // 如果蛇不是向右前进的时候，按左键，执行向左前进
        {
            status = LEFT;
        }
        else if (GetAsyncKeyState(VK_RIGHT) && status != LEFT) // 如果蛇不是向左前进的时候，按右键，执行向右前进
        {
            status = RIGHT;
        }
        if (GetAsyncKeyState(VK_SPACE)) // 空格暂停
        {
            while (1)
            {
                Sleep(300);
                if (GetAsyncKeyState(VK_SPACE)) // 再次按空格改变状态
                {
                    break;
                }
            }
        }
        else if (GetAsyncKeyState(VK_ESCAPE))
        {
            status = ESC; // 按esc键,直接到结束界面
            if (endGame())
            {
                Sleep(500);
                system("cls");
                break;
            }
        }
        else if (GetAsyncKeyState(VK_F1)) // 按F1键，加速
        {
            speedUp();
            gotoxy(60, 13);
            printf("当前分数:%d分,当前速度%d", score, speed);
        }
        else if (GetAsyncKeyState(VK_F2)) // 按F2键，减速
        {
            speedDown();
            gotoxy(60, 13);
            printf("当前分数:%d分,当前速度%d", score, speed);
        }
        snakeMove();
    }
}
void speedUp(void)
{
    if (speed <= 280)
        speed += 20;
}
void speedDown(void)
{
    if (speed >= 120)
        speed -= 20;
}
int endGame(void)
{
    char x = 0;
    char judge[5];

    getchar();
    gotoxy(60, 9);
    printf("确定退出吗?(Yes/No)");
    gotoxy(60, 11);
    s_gets(judge, 5);

    if (strcmp(judge, "Yes") == 0)
    {
        Sleep(250);
        system("cls");
        gotoxy(40, 11);
        printf("\tBye!");
        x = 1;
    }
    else
        x = 0;

    return x;
}
char *s_gets(char *st, int n)
{
    char *ret_val;
    char *find;

    gotoxy(60, 11);
    ret_val = fgets(st, n, stdin);
    if (ret_val)
    {
        find = strchr(st, '\n');
        if (find)
            *find = '\0';
        else
            while (getchar() != '\n')
                continue;
    }

    return ret_val;
}
void frees(snake *s)
{
    snake *current = s;

    while (current)
    {
        current = s;
        s = current->next;
        free(current);
    }
}